package com.ljt.yupictureddd.shared.manager.websocket;

import cn.hutool.core.collection.CollUtil;
import cn.hutool.core.util.ObjUtil;
import cn.hutool.json.JSONUtil;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.module.SimpleModule;
import com.fasterxml.jackson.databind.ser.std.ToStringSerializer;
import com.ljt.yupictureddd.shared.manager.websocket.disruptor.PictureEditEventProducer;
import com.ljt.yupictureddd.shared.manager.websocket.model.PictureEditActionEnums;
import com.ljt.yupictureddd.shared.manager.websocket.model.PictureEditMessageTypeEnums;
import com.ljt.yupictureddd.shared.manager.websocket.model.PictureEditRequestMessage;
import com.ljt.yupictureddd.shared.manager.websocket.model.PictureEditResponseMessage;
import com.ljt.yupictureddd.domain.user.entity.User;
import com.ljt.yupictureddd.application.service.UserApplicationService;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Component;
import org.springframework.web.socket.CloseStatus;
import org.springframework.web.socket.TextMessage;
import org.springframework.web.socket.WebSocketSession;
import org.springframework.web.socket.handler.TextWebSocketHandler;

import java.io.IOException;
import java.util.Map;
import java.util.Set;
import java.util.concurrent.ConcurrentHashMap;

/**
 * 图片编辑WebSocket处理器
 */
@Slf4j
@Component
@RequiredArgsConstructor
public class PictureEditHandler extends TextWebSocketHandler {
    // 每张图片的编辑状态,key:pictureId, value:当前正在编辑的用户Id. 图片Id->对应一个正在编辑的用户
    private final Map<Long, Long> pictureEditingUsers = new ConcurrentHashMap<>();
    // 保存所有连接的对话.key:pictureId, value:Session ,图片id->对应一个对话集合
    private final Map<Long, Set<WebSocketSession>> pictureSessions = new ConcurrentHashMap<>();

    private final UserApplicationService userApplicationService;
    private final PictureEditEventProducer pictureEditEventProducer;


    /**
     * 连接成功后,保存连接对话
     */
    @Override
    public void afterConnectionEstablished(WebSocketSession session) throws Exception {
        super.afterConnectionEstablished(session);
        // 获取参数
        User user = (User) session.getAttributes().get("user");
        Long pictureId = (Long) session.getAttributes().get("pictureId");
        // 集合添加信息
        pictureSessions.putIfAbsent(pictureId, ConcurrentHashMap.newKeySet());
        // 构造响应请求,给前段返回,进入房间了.
        PictureEditResponseMessage pictureEditResponseMessage = new PictureEditResponseMessage();
        pictureEditResponseMessage.setType(PictureEditMessageTypeEnums.INFO.getText());
        String message = String.format("用户 %s 加入编辑", user.getUserName());
        pictureEditResponseMessage.setMessage(message);
        pictureEditResponseMessage.setUser(userApplicationService.getUserVO(user));
        // 广播给所有用户
        broadcastToPicture(pictureId, pictureEditResponseMessage, session);
    }

    /**
     * 收到前端发送的消息,根据消息类别处理消息
     */
    @Override
    protected void handleTextMessage(WebSocketSession session, TextMessage message) throws Exception {
        super.handleTextMessage(session, message);
        // 获取消息内容,讲JSON转换为PictureEditRequestMessage
        PictureEditRequestMessage pictureEditRequestMessage = JSONUtil.toBean(message.getPayload(), PictureEditRequestMessage.class);
        String type = pictureEditRequestMessage.getType();
        PictureEditMessageTypeEnums picTypeEnums = PictureEditMessageTypeEnums.getEnumByValue(type);
        String editAction = pictureEditRequestMessage.getEditAction();
        // 获取公共参数
        User user = (User) session.getAttributes().get("user");
        Long pictureId = (Long) session.getAttributes().get("pictureId");
        // 生产消息到disruptor
        pictureEditEventProducer.publishEvent(pictureEditRequestMessage,session,user,pictureId);
    }

    /**
     * 进入编辑状态
     */
    public void handlerEditActionMessage(PictureEditRequestMessage pictureEditRequestMessage, WebSocketSession session, User user, Long pictureId) throws IOException {
        // 没有用户进入该图片
        if (!pictureEditingUsers.containsKey(pictureId)) {
            // 在编辑图片集合中添加记录
            pictureEditingUsers.put(pictureId, user.getId());
            // 构造响应
            PictureEditResponseMessage pictureEditResponseMessage = new PictureEditResponseMessage();
            pictureEditResponseMessage.setType(PictureEditMessageTypeEnums.INFO.getValue());
            pictureEditResponseMessage.setMessage(String.format("用户 %s 开始编辑图片", user.getUserName()));
            pictureEditResponseMessage.setUser(userApplicationService.getUserVO(user));
            // 广播给全部的用户
            this.broadcastToPicture(pictureId, pictureEditResponseMessage);
        }
    }

    /**
     * 处理编辑操作
     */
    public void handlerEnterEditMessage(PictureEditRequestMessage pictureEditRequestMessage, WebSocketSession session, User user, Long pictureId) throws IOException {
        // 获取参数
        Long editingUserId = pictureEditingUsers.get(pictureId);
        String editAction = pictureEditRequestMessage.getEditAction();
        PictureEditActionEnums pictureEditActionEnums = PictureEditActionEnums.getEnumByValue(editAction);
        // 校验参数
        if (pictureEditActionEnums == null) {
            log.error("无效的编辑错误");
            return;
        }
        // 确定是当前的编辑者
        if (editingUserId != null && editingUserId.equals(user.getId())) {
            // 构造响应
            PictureEditResponseMessage pictureEditResponseMessage = new PictureEditResponseMessage();
            pictureEditResponseMessage.setType(PictureEditMessageTypeEnums.INFO.getValue());
            pictureEditResponseMessage.setMessage(String.format("%s 执行 %s", user.getUserName(), pictureEditActionEnums.getText()));
            pictureEditResponseMessage.setEditAction(pictureEditActionEnums.getValue());
            pictureEditResponseMessage.setUser(userApplicationService.getUserVO(user));
            // 广播给出了自己的用户
            this.broadcastToPicture(pictureId, pictureEditResponseMessage, session);
        }

    }

    /**
     * 退出编辑状态
     */
    public void handlerExitEditMessage(PictureEditRequestMessage pictureEditRequestMessage, WebSocketSession session, User user, Long pictureId) throws IOException {
        // 获得当前在编辑的用户
        Long editingUserId = pictureEditingUsers.get(pictureId);
        // 确认是当前的编辑的用户
        if (editingUserId != null && editingUserId.equals(user.getId())) {
            // 移除用户的编辑状态
            pictureEditingUsers.remove(pictureId);
            // 构造响应
            PictureEditResponseMessage pictureEditResponseMessage = new PictureEditResponseMessage();
            pictureEditResponseMessage.setType(PictureEditMessageTypeEnums.INFO.getValue());
            pictureEditResponseMessage.setMessage(String.format("用户 %s 停止编辑图片", user.getUserName()));
            pictureEditResponseMessage.setUser(userApplicationService.getUserVO(user));
            // 广播给全部的用户
            this.broadcastToPicture(pictureId, pictureEditResponseMessage);
        }
    }

    /**
     *
     */
    @Override
    public void afterConnectionClosed(WebSocketSession session, CloseStatus status) throws Exception {
        super.afterConnectionClosed(session, status);
        // 获取参数
        User user = (User) session.getAttributes().get("user");
        Long pictureId = (Long) session.getAttributes().get("pictureId");
        // 移除当前用户的编辑状态
        this.handlerExitEditMessage(null, session, user, pictureId);
        // 删除对话
        Set<WebSocketSession> webSocketSessions = pictureSessions.get(pictureId);
        if (!webSocketSessions.isEmpty()) {
            webSocketSessions.remove(session);
            // 如果这个图片没有会话了，就删除图片会话记录
            if (webSocketSessions.isEmpty()) {
                pictureSessions.remove(pictureId);
            }
        }
        // 构造响应
        // 构造响应
        PictureEditResponseMessage pictureEditResponseMessage = new PictureEditResponseMessage();
        pictureEditResponseMessage.setType(PictureEditMessageTypeEnums.INFO.getValue());
        pictureEditResponseMessage.setMessage(String.format("用户 %s 离开了编辑", user.getUserName()));
        pictureEditResponseMessage.setUser(userApplicationService.getUserVO(user));
        // 广播给全部的用户
        this.broadcastToPicture(pictureId, pictureEditResponseMessage);
    }

    /**
     * 通用方法,广播给该图片的全部用户(支持排除某些Session)
     *
     * @param pictureId
     */
    private void broadcastToPicture(Long pictureId, PictureEditResponseMessage pictureEditResponseMessage, WebSocketSession excludeSession) throws IOException {
        // 获得对话
        Set<WebSocketSession> webSocketSessions = pictureSessions.get(pictureId);
        // 对话不为空,进行广播
        if (CollUtil.isNotEmpty(webSocketSessions)) {
            // 注意注意⚠,里面有Long类型的数据,小心数据精度丢失
            // websocket不支持序列化转换,这里用jackson,自己配置一下自己new出来的objectmapper,进行转换
            ObjectMapper objectMapper = new ObjectMapper();
            SimpleModule module = new SimpleModule();
            module.addSerializer(Long.class, ToStringSerializer.instance);
            module.addSerializer(Long.TYPE, ToStringSerializer.instance);
            objectMapper.registerModule(module);
            String jsonStr = objectMapper.writeValueAsString(pictureEditResponseMessage);
            // String jsonStr = JSONUtil.toJsonStr(pictureEditResponseMessage);
            TextMessage textMessage = new TextMessage(jsonStr);
            for (WebSocketSession currentSession : webSocketSessions) {
                // 排除自己的会话要发送消息
                if (ObjUtil.equal(excludeSession, currentSession)) {
                    continue;
                }
                if (currentSession.isOpen()) {
                    currentSession.sendMessage(textMessage);
                }
            }
        }
    }

    /**
     * 通用方法,广播给该图片的全部用户(可以不传排除的session)
     */
    private void broadcastToPicture(Long pictureId, PictureEditResponseMessage pictureEditResponseMessage) throws IOException {
        broadcastToPicture(pictureId, pictureEditResponseMessage, null);
    }

}
